package com.ps.dao;


import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;


import com.ps.util.PaginationSupport;



public class BaseDaoSupport<T> extends HibernateDaoSupport{

	protected final Logger log = Logger.getLogger(getClass());
	protected final Class<T> entityClass;
    
    
    
	public BaseDaoSupport() {
		entityClass = this.getClazz();
	}


	public boolean exists(Integer id) {
		Assert.notNull(id);
		T entity = (T)this.getHibernateTemplate().get(entityClass, id);
		return entity != null;
	}


	public T findById(Integer id) {
		log.debug("getting " + entityClass.getName() + " instance with id: " + id);
		Assert.notNull(id);
		try {
			T entity = (T) this.getHibernateTemplate().get(entityClass, id);
			if (entity == null) {
				log.warn("Uh oh, '" + entityClass.getName() + "' object with id '"
						+ id + "' not found...");
				return null;
			}
			return entity;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}


	
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		log.debug("finding all " + entityClass.getName() + " instances");
		try {
			String queryString = "from " + entityClass.getName();
			return getHibernateTemplate().find(queryString);
		} catch (RuntimeException re) {
			log.error("find all failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	public List findByExample(T instance) {
		log.debug("finding " + entityClass.getName() + " instance by example");
		try {
			List results = getHibernateTemplate().findByExample(instance);
			log.debug("find by example successful, result size: "
					+ results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(final DetachedCriteria detachedCriteria) {
		
		return this.getHibernateTemplate().executeFind(new HibernateCallback(){
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				Criteria criteria = detachedCriteria.getExecutableCriteria(session);  
				return criteria.list();  
			}});
		
	}

	public PaginationSupport<T> findPageByCriteria(final DetachedCriteria detachedCriteria, final int pageSize,final int startIndex,final List<Order> orderList) {
			
		return getHibernateTemplate().executeWithNativeSession(new HibernateCallback<PaginationSupport<T>>() {

			@SuppressWarnings("unchecked")
			public PaginationSupport<T> doInHibernate(Session session) throws HibernateException,
					SQLException {
				DetachedCriteria clone = (DetachedCriteria) SerializationUtils.clone(detachedCriteria); 
				Criteria criteria = clone.getExecutableCriteria(session);
				int totalCount = ((Integer) criteria.setProjection(
						Projections.rowCount()).uniqueResult()).intValue();
				for (Order order : orderList) {
					detachedCriteria.addOrder(order);
				}
				Criteria criteria1 = detachedCriteria.getExecutableCriteria(session);
				List<T> items = criteria1.setFirstResult(startIndex).setMaxResults(pageSize).list();
				clone.setProjection(null);
				PaginationSupport<T> ps = new PaginationSupport<T>(items,
						totalCount, pageSize, startIndex);
				return ps;
			}
		});
		

	}  


	public PaginationSupport<T> findPageByCriteria(final DetachedCriteria detachedCriteria,
				final int pageSize,final int startIndex,final Order order) {
		 List<Order> orderList = new ArrayList<Order>();
		 orderList.add(order);
		 return this.findPageByCriteria(detachedCriteria, pageSize, startIndex, orderList);
	}  
	

	
	
	public Integer save(T transientInstance) {
		log.debug("saving " + entityClass.getName() + " instance");
		try {
			Integer id = (Integer) getHibernateTemplate().save(transientInstance);
			log.debug("save successful");
			return  id;
			
		} catch (RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}
	}	

	public void delete(T persistentInstance) {
		log.debug("deleting " + entityClass.getName() + " instance");
		try {
			getHibernateTemplate().delete(persistentInstance);
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
	}	
	
	public void delete(Integer id) {
		log.debug("deleting " + entityClass.getName() + " instance id = " + id);
		try {
			getHibernateTemplate().delete(findById(id));
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
		
	}
	
	public void update(T instance) {
		Assert.notNull(instance);
		try {
			this.getHibernateTemplate().update(instance);
			log.debug("update successful");
			
		} catch (RuntimeException re) {
			log.error("update failed", re);
			throw re;
		}		
		

	}

	public T merge(T detachedInstance) {
		Assert.notNull(detachedInstance);
		log.debug("merging " + entityClass.getName() + " instance");
		try {
			T  result = getHibernateTemplate().merge(detachedInstance);
			log.debug("merge successful");
			return result;
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}

	public void saveOrUpdate(T instance) {
		Assert.notNull(instance);
		log.debug("saveOrUpdate " + entityClass.getName() + " instance");
		try {
			getHibernateTemplate().saveOrUpdate(instance);
			log.debug("saveOrUpdate successful");
		} catch (RuntimeException re) {
			log.error("saveOrUpdate failed", re);
			throw re;
		}
	}

	
	public void flush() {
		getSession().flush();
	}
	
	@SuppressWarnings("unchecked")
	public List<T> findByProperty(String propertyName, Object value) {
		log.debug("finding " + entityClass.getName() + " instance with property: " + propertyName
				+ ", value: " + value);
		try {
			String queryString = "from " + entityClass.getName() + " as model where model."
					+ propertyName + "= ?";
			return getHibernateTemplate().find(queryString, value);
		} catch (RuntimeException re) {
			log.error("find by property name failed", re);
			throw re;
		}
	}

	public int getCountByProperty(String propertyName) {
		try {
			DetachedCriteria deCriteria = DetachedCriteria.forClass(entityClass);
			deCriteria.setProjection(Projections.count(propertyName));
			
		    return (Integer)this.getHibernateTemplate().findByCriteria(deCriteria).get(0);
		} catch (RuntimeException re) {
			log.error("find by property name count failed", re);
			throw re;
		}

	} 
	
	public int getCountByProperty(String propertyName, Object value) {
		try {
			DetachedCriteria deCriteria = DetachedCriteria.forClass(entityClass);
			deCriteria.setProjection(Projections.count(propertyName));
			deCriteria.add(Restrictions.eq(propertyName, value));
			return (Integer) this.getHibernateTemplate().findByCriteria(deCriteria).get(0);
		} catch (RuntimeException re) {
			log.error("find by property name count failed", re);
			throw re;

		}
	}
	
	
	@SuppressWarnings("unchecked")
	private Class<T> getClazz(){
		 return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 
	}
}
